package org.dizena.common.trace.method.handler;

import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
import org.dizena.common.base.enums.EnumHeaderKey;
import org.dizena.common.base.enums.EnumLogLevel;
import org.dizena.common.except.BusinessException;
import org.dizena.common.logger.bean.LogInfo;
import org.dizena.common.logger.bean.LogLocEvent;
import org.dizena.common.utils.ReqUtil;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.http.ResponseEntity;
import org.springframework.util.StopWatch;
import org.springframework.web.multipart.MultipartFile;

import java.io.File;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;

public class MethodLogInterceptor implements MethodInterceptor {

    private final ApplicationEventPublisher publisher;

    public MethodLogInterceptor(ApplicationEventPublisher publisher) {
        this.publisher = publisher;
    }

    @Override
    public Object invoke(MethodInvocation invocation) throws Throwable {
        StopWatch stopWatch = new StopWatch();
        stopWatch.start();
        LogInfo logInfo = getLogInfo(invocation);
        try {

            Object result = invocation.proceed();
            stopWatch.stop();
            logInfo.setCostTime(stopWatch.getLastTaskTimeMillis());
            logInfo.setResponseTime(System.currentTimeMillis());
            if (result != null && result instanceof ResponseEntity) {
                ResponseEntity entity = (ResponseEntity) result;
                logInfo.setResponseBody(entity.getBody());
            } else {
                logInfo.setResponseBody(result);
            }
            publisher.publishEvent(new LogLocEvent(this, logInfo, EnumLogLevel.INFO));
            return result;
        } catch (Throwable e) {
            stopWatch.stop();
            logInfo.setCostTime(stopWatch.getLastTaskTimeMillis());
            logInfo.setResponseTime(System.currentTimeMillis());
            if (e instanceof BusinessException) {
                BusinessException b = (BusinessException) e;
                String error = e + ",code:" + b.getCode() + ",msg:" + b.getMessage();
                logInfo.setException(error);
                publisher.publishEvent(new LogLocEvent(this, logInfo, EnumLogLevel.ERROR));
            } else {
                logInfo.setException(e.getMessage() + " " + e.getStackTrace()[0]);
                publisher.publishEvent(new LogLocEvent(this, logInfo, EnumLogLevel.ERROR));
            }
            throw e;
        }
    }

    private LogInfo getLogInfo(MethodInvocation invocation) {
        LogInfo logInfo = new LogInfo();
        Map<String, String> header = ReqUtil.getAllHeaderMap();
        logInfo.setTraceId(header.get(EnumHeaderKey.TRACE_ID.getKey()));
        logInfo.setClientIp(header.get(EnumHeaderKey.REQUEST_IP.getKey()));
        logInfo.setRequestHeader(new HashMap<>(header));
        logInfo.setRequestTime(System.currentTimeMillis());
        HttpServletRequest request = ReqUtil.getRequest();
        if (null == request) {
            Object[] args = invocation.getArguments();
            Map<String, Object> map = new HashMap<>();
            int i = 0;
            for (Object arg : args) {
                map.put(++i + "", arg);
            }
            logInfo.setRequestParam(map);
        } else {
            logInfo.setRequestUrl(request.getRequestURI());
            logInfo.setRequestMethod(request.getMethod());

            logInfo.setRequestParam(getParam(invocation));
        }
        return logInfo;
    }

    private Map<String, Object> getParam(MethodInvocation invocation) {
        Object[] args = invocation.getArguments();
        Method method = invocation.getMethod();
        Parameter[] parameters = method.getParameters();
        Map<String, Object> map = new LinkedHashMap<>();
        HttpServletRequest request = ReqUtil.getRequest();
        if (parameters != null && parameters.length > 0) {
            for (int i = 0; i < args.length; i++) {
                Object arg = args[i];
                if (arg == null || arg instanceof HttpServletResponse) {
                    continue;
                } else if (arg instanceof HttpServletRequest) {
                    Enumeration<String> parameterNames = request.getParameterNames();
                    while (parameterNames.hasMoreElements()) {
                        String key = parameterNames.nextElement();
                        map.put(key, request.getParameter(key));
                    }
                } else if (arg instanceof MultipartFile) {
                    map.put(parameters[i].getName(), ((MultipartFile) arg).getOriginalFilename());
                } else if (arg instanceof File) {
                    map.put(parameters[i].getName(), ((File) arg).getPath());
                } else if (arg instanceof Throwable) {

                } else if (arg.getClass().isArray()) {
                    Object[] sub = (Object[]) arg;
                    Object[] newArray = new Object[sub.length];
                    for (int j = 0; j < sub.length; j++) {
                        if (sub[j] instanceof MultipartFile) {
                            newArray[j] = ((MultipartFile) sub[j]).getOriginalFilename();
                        } else if (sub[j] instanceof File) {
                            newArray[j] = ((File) sub[j]).getPath();
                        } else {
                            newArray[j] = sub[j];
                        }
                    }
                    map.put(parameters[i].getName(), newArray);
                } else {
                    map.put(parameters[i].getName(), arg);
                }
            }
        }
        return map;
    }
}
